#!/usr/bin/env python
# encoding: utf-8
"""
Generate code to process OME-XML from an OME-XML schema.
"""

#
#  Copyright (C) 2007 - 2014 University of Dundee. All rights reserved.
#
#
#  This program is free software; you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation; either version 2 of the License, or
#  (at your option) any later version.
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License along
#  with this program; if not, write to the Free Software Foundation, Inc.,
#  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#

from __future__ import print_function
from __future__ import unicode_literals

import logging
import sys
import os
import re
import codecs

# Needed to import modeltools
sys.path.insert(0, os.path.join(os.path.dirname(os.path.realpath(__file__)), 'python'))

from util import odict

from ome.modeltools.template import TemplateInfo
from ome.modeltools import config
from ome.modeltools import language
from ome.modeltools import generateds

try:
    from genshi.template import NewTextTemplate
except ImportError:
    print("""The genshi module was not found, but is required by xsd-fu.
If this is provided by your distribution, install the
"python-genshi" package or its equivalent.  Otherwise, run
"pip install genshi" (you may need to install pip first).""", file=sys.stderr)

    sys.exit(1)

from configobj import ConfigObj
from getopt import gnu_getopt, GetoptError
from glob import glob
from xml import sax

def guard(filename):
    filename = "_".join(filename.split(os.path.sep))
    filename = filename.upper()
    filename = re.sub("[^A-Z0-9]", "_", filename)
    return filename

def usage(error):
    """
    Prints usage so that we don't have to. :)
    """
    java = language.create("Java", config.DEFAULT_NAMESPACE, "/path/to/schemas")
    cpp = language.create("C++", config.DEFAULT_NAMESPACE, "/path/to/schemas")

    cmd = sys.argv[0]
    print("""Error: %s

xsd-fu: Generate classes from an OME-XML schema definition
Usage: %s command [options…] -o output_dir schema_files…

Options:
  -d, --dry-run                              Do not create output files
  --debug                                    Enable xsd-fu and template debugging
  -l, --language=lang                        Generated language
  --metadata-package=pkg                     Metadata package
  --ome-xml-metadata-package=pkg             OME-XML metadata class package
  --ome-xml-model-package=pkg                OME-XML model package
  --ome-xml-model-enums-package=pkg          OME-XML model enum package
  --ome-xml-model-enum-handlers-package=pkg  OME-XML model enum handler package
  -o, --output-directory=dir                 Generated output directory
  --print-depends                            Output command dependencies
  --print-generated                          Output generated file names
  -q, --quiet                                Do not output any messages
  -t, --template-path=path                   Genshi template path
  -v, --verbose                              Output generated file names
  -n, --xsd-namespace                        XML schema namespace

Available subcommands:
  doc_gen
  metadata
  omero_metadata
  omero_model
  omexml_metadata
  omexml_model
  omexml_model_enums
  omexml_model_enum_handlers
  omexml_model_enum_includeall
  tab_gen

Default XSD namespace: "%s"

Default Java OME-XML package: "%s"
Default Java OME-XML enum package: "%s"
Default Java OME-XML enum handler package: "%s"
Default Java metadata package: "%s"
Default Java OME-XML metadata package: "%s"

Default C++ OME-XML package: "%s"
Default C++ OME-XML enum package: "%s"
Default C++ metadata package: "%s"
Default C++ OME-XML metadata package: "%s"

Examples:
  %s -l Java -n '%s' --ome-xml-model-package=%s -o omexml /path/to/schemas/ome.xsd
  %s -l C++ -n '%s' --ome-xml-model-package=%s -o omexml /path/to/schemas/ome.xsd

Report bugs to OME Devel <ome-devel@lists.openmicroscopy.org.uk>""" % \
    (error,
     cmd,
     java.modelNamespace,
     java.omexml_model_package, java.omexml_model_enums_package, java.omexml_model_omexml_model_enum_handlers_package, java.metadata_package, java.omexml_metadata_package,
     cpp.omexml_model_package, cpp.omexml_model_enums_package, cpp.metadata_package, cpp.omexml_metadata_package,
     cmd, java.modelNamespace, java.omexml_model_package,
     cmd, cpp.modelNamespace, cpp.omexml_model_package))
    sys.exit(2)

def main(model, opts):
    """
    Default main() that parses command line options and generates
    classes in the output directory.
    """
    used_files = set()
    for file in opts.args:
        used_files.add(os.path.realpath(file))

    fu = TemplateInfo(opts.outdir, opts.lang.omexml_model_package)
    template = NewTextTemplate(codecs.open(opts.lang.templatepath('CLASS'), encoding='utf-8').read(), encoding='utf-8')
    used_files.add(os.path.realpath(opts.lang.templatepath('CLASS')))
    base, filename = os.path.split(opts.lang.templatepath('CLASS'))
    name, extension = os.path.splitext(filename)
    for obj in model.objects.values():
        if obj.name in opts.lang.model_type_map.keys():
            continue

        parents = model.resolve_parents(obj.name)

        fu.SOURCE_TYPE = opts.filetype;

        CUSTOM_CLASS_TEMPLATE = os.path.join(
            base, '%s_%s%s' % (name, obj.name, extension))
        customContent = ''
        if os.path.exists(CUSTOM_CLASS_TEMPLATE):
            customTemplate = NewTextTemplate(codecs.open(CUSTOM_CLASS_TEMPLATE, encoding='utf-8'), encoding='utf-8')
            used_files.add(os.path.realpath(CUSTOM_CLASS_TEMPLATE))
            customContent = customTemplate.generate(
                fu=fu, klass=obj, parents=parents, model=model, opts=opts, lang=opts.lang, debug=opts.debug)
            customContent = customContent.render(encoding='utf-8')
        customTemplates = glob(os.path.join(
                base, "%s_%s_update_*%s" % (name, obj.name, extension)))
        # BEGIN Custom templates for update()
        customUpdatePropertyContent = dict()
        for customTemplate in customTemplates:
            start = customTemplate.rfind("_") + 1
            end = customTemplate.rfind(".")
            propertyName = customTemplate[start:end]
            used_files.add(os.path.realpath(customTemplate))
            customTemplate = NewTextTemplate(codecs.open(customTemplate, encoding='utf-8'), encoding='utf-8')
            content = customTemplate.generate(
                fu=fu, klass=obj, parents=parents, model=model, opts=opts, lang=opts.lang, debug=opts.debug)
            content = content.render(encoding='utf-8')
            customUpdatePropertyContent[propertyName] = content
        # END Custom templates for update()
        # BEGIN Custom templates for asXMLElement()
        customTemplates = glob(os.path.join(
                base, "%s_%s_asXMLElement_*%s" % (name, obj.name, extension)))
        customAsXMLElementPropertyContent = dict()
        for customTemplate in customTemplates:
            start = customTemplate.rfind("_") + 1
            end = customTemplate.rfind(".")
            propertyName = customTemplate[start:end]
            used_files.add(os.path.realpath(customTemplate))
            customTemplate = NewTextTemplate(codecs.open(customTemplate, encoding='utf-8'), encoding='utf-8')
            content = customTemplate.generate(
                fu=fu, klass=obj, parents=parents, model=model, opts=opts, lang=opts.lang, debug=opts.debug)
            content = content.render(encoding='utf-8')
            customAsXMLElementPropertyContent[propertyName] = content
        # END Custom templates for asXMLElment()

        try:
            fullname = opts.lang.generatedFilename(obj.name, opts.filetype)
            fullname = os.path.join("ome", "xml", "model", fullname)
        except ModelProcessingError:
            continue

        fu.GUARD = guard(fullname)

        fullname = os.path.join(opts.outdir, fullname)

        if opts.print_generated:
            print(fullname)

        outputDirectory = os.path.dirname(fullname)
        if not opts.dryrun and not os.path.exists(outputDirectory):
            os.makedirs(outputDirectory)

        classContent = template.generate(
            fu=fu, klass=obj, model=model, opts=opts, lang=opts.lang, debug=opts.debug,
            customContent=customContent,
            customUpdatePropertyContent=customUpdatePropertyContent,
            customAsXMLElementPropertyContent=customAsXMLElementPropertyContent)
        classContent = classContent.render(encoding='utf-8')

        if not opts.dryrun:
            f = codecs.open(fullname, "w", encoding='utf-8')
            f.write(classContent.decode('utf-8'))
            f.close()

    if opts.print_depends:
        for file in sorted(used_files):
            print(file)


def enum_value_name(value, isUnitsEnum):
    """
    Returns a valid Java enumeration name for an OME-XML enumeration value.
    """
    if re.match(r'^\d+', value):
        value = value.replace("0", "zero")
        value = value.replace("1", "one")
        value = value.replace("2", "two")
        value = value.replace("3", "three")
        value = value.replace("4", "four")
        value = value.replace("5", "five")
        value = value.replace("6", "six")
        value = value.replace("7", "seven")
        value = value.replace("8", "eight")
        value = value.replace("9", "nine")
    value = value.replace(' ', "")
    value = value.replace("-", "")

    if isUnitsEnum:
        """ replace SI multiple prefixes """
        value = re.sub("^M", "mega", value)
        value = re.sub("^G", "giga", value)
        value = re.sub("^T(?!o)", "tera", value)
        value = re.sub("^P(?!a)", "peta", value)
        value = re.sub("^E", "exa", value)
        value = re.sub("^Z", "zetta", value)
        value = re.sub("^Y", "yotta", value)

        """ µ is &micro; &#181; &#xB5; 'micro sign Mu' """
        """ replace all µ to avoid compile errors """
        value = value.replace('µ', "micro")

        """ replace all ° and Å to avoid compile errors """
        value = value.replace('°', "degree")
        value = value.replace('Å', "angstrom")

    return value.upper()

def enumTypesMain(model, opts):
    """
    Default main() that parses command line options and generates Java
    classes for enumeration types in an output directory.
    """

    used_files = set()
    for file in opts.args:
        used_files.add(os.path.realpath(file))

    fu = TemplateInfo(opts.outdir, opts.lang.omexml_model_enums_package)
    template = NewTextTemplate(codecs.open(opts.lang.templatepath('ENUM'), encoding='utf-8').read(), encoding='utf-8')
    used_files.add(os.path.realpath(opts.lang.templatepath('ENUM')))
    for obj in model.objects.values():
        for prop in obj.properties.values():
            if not prop.isEnumeration:
                continue

            try:
                fullname = opts.lang.generatedFilename(prop.langType, opts.filetype)
                fullname = os.path.join("ome", "xml", "model", "enums", fullname)
            except ModelProcessingError:
                continue

            fu.GUARD = guard(fullname)
            fu.SOURCE_TYPE = opts.filetype;

            fullname = os.path.join(opts.outdir, fullname)

            if opts.print_generated:
                print(fullname)

            outputDirectory = os.path.dirname(fullname)
            if not opts.dryrun and not os.path.exists(outputDirectory):
                os.makedirs(outputDirectory)

            classContent = template.generate(fu=fu, klass=prop, opts=opts, lang=opts.lang, debug=opts.debug,
                                             enum_value_name=enum_value_name).render(encoding='utf-8')

            if not opts.dryrun:
                f = codecs.open(fullname, "w", encoding='utf-8')
                f.write(classContent.decode('utf-8'))
                f.close()

    if opts.print_depends:
        for file in sorted(used_files):
            print(file)

def enumTypesIncludeAll(model, opts):
    """
    Generate header to include all enum headers.
    """

    used_files = set()
    for file in opts.args:
        used_files.add(os.path.realpath(file))

    fu = TemplateInfo(opts.outdir, opts.lang.omexml_model_enums_package)
    template = NewTextTemplate(codecs.open(opts.lang.templatepath('ENUM_INCLUDEALL'), encoding='utf-8').read(), encoding='utf-8')
    used_files.add(os.path.realpath(opts.lang.templatepath('ENUM_INCLUDEALL')))
    headers = model.getEnumHeaders()

    fullname = opts.lang.generatedFilename("enums", language.TYPE_HEADER)
    fullname = os.path.join("ome", "xml", "model", fullname)

    fu.GUARD = guard(fullname)
    fu.SOURCE_TYPE = language.TYPE_HEADER;
    fu.HEADERS = sorted(headers);

    fullname = os.path.join(opts.outdir, fullname)

    if opts.print_generated:
        print(fullname)

    outputDirectory = os.path.dirname(fullname)
    if not opts.dryrun and not os.path.exists(outputDirectory):
        os.makedirs(outputDirectory)

    classContent = template.generate(fu=fu, model=model, opts=opts, lang=opts.lang, debug=opts.debug).render(encoding='utf-8')

    if not opts.dryrun:
        f = codecs.open(fullname, "w", encoding='utf-8')
        f.write(classContent.decode('utf-8'))
        f.close()

    if opts.print_depends:
        for file in sorted(used_files):
            print(file)

def enumHandlersMain(model, opts):
    """
    Default main() that parses command line options and generates
    classes for enumeration handlers in an output directory.
    """

    used_files = set()
    for file in opts.args:
        used_files.add(os.path.realpath(file))

    fu = TemplateInfo(opts.outdir, opts.lang.omexml_model_omexml_model_enum_handlers_package)
    base = os.path.join(os.path.split(os.path.abspath(__file__))[0], "cfg")
    configobj = ConfigObj(os.path.join(base, "enum_handler.cfg"))
    template = NewTextTemplate(codecs.open(opts.lang.templatepath('ENUM_HANDLER'), encoding='utf-8').read(), encoding='utf-8')
    used_files.add(os.path.realpath(opts.lang.templatepath('ENUM_HANDLER')))
    for obj in model.objects.values():
        for prop in obj.properties.values():
            if not prop.isEnumeration:
                continue

            try:
                fullname = opts.lang.generatedFilename(prop.langType + "EnumHandler", opts.filetype)
                fullname = os.path.join("ome", "xml", "model", "enums", "handlers", fullname)
            except ModelProcessingError:
                continue

            fu.GUARD = guard(fullname)
            fu.SOURCE_TYPE = opts.filetype;

            fullname = os.path.join(opts.outdir, fullname)

            if opts.print_generated:
                print(fullname)

            outputDirectory = os.path.dirname(fullname)
            if not opts.dryrun and not os.path.exists(outputDirectory):
                os.makedirs(outputDirectory)

            classContent = template.generate(fu=fu, klass=prop, model=model, opts=opts, lang=opts.lang, debug=opts.debug,
                             enum_value_name=enum_value_name, config=configobj).render(encoding='utf-8')

            if not opts.dryrun:
                f = codecs.open(fullname, "w", encoding='utf-8')
                f.write(classContent.decode('utf-8'))
                f.close()

    if opts.print_depends:
        for file in sorted(used_files):
            print(file)

def max_occurs_under_parent(model, parent, name):
    """
    Returns the maximum occurrances of a given object under its current
    parent.
    """
    parent = model.getObjectByName(parent)
    for prop in parent.properties.values():
        if prop.name == name:
            return prop.maxOccurs
    return 0

def resolve_hierarchy(results, model, names, func, child=None, key=None, level=0, min_occurs=2):
    """
    Resolves a representation of the hierarchical structure of element names.
    Updates the result list and returns a boolean True if the result list is
    multi-path, a boolean False otherwise.
    """
    is_multi_path = False
    if names is None:
        return is_multi_path
    for name in names.keys():
        # Set our key and instantiate our index list if we're the first level
        if level == 1:
            key = name
            results[key] = list()
        # We only want to add the index if the object count at this level
        # is greater than 1.
        max_occurs = max_occurs_under_parent(model, name, child)
        if len(names.keys()) > 1:
            is_multi_path = True
        is_child_multi_path = resolve_hierarchy(results, model, names[name], func, name, key, level + 1, min_occurs)
        if is_child_multi_path:
            is_multi_path = True
        if child is not None and max_occurs >= min_occurs:
            results[key].append(func(child, max_occurs, level))
    return is_multi_path

def processMetadataTemplate(template, outputFilename, model, opts):
    """
    Actually does the work of processing the model and providing the model
    to a given "Metadata" template for interface or concrete class code
    generation.
    """
    args = opts.args
    template = opts.lang.templatepath(template)

    used_files = set()
    for file in opts.args:
        used_files.add(os.path.realpath(file))

    indexes = dict()
    parents = dict()
    is_multi_path = dict()
    for obj in model.objects.values():
        name = obj.name
        indexes[name] = odict()
        parents[name] = model.resolve_parents(name)
        is_multi_path[name] = resolve_hierarchy(indexes[name], model,
                            {name: parents[name]},
                            opts.lang.index_signature)

    object_headers = model.getObjectHeaders()

    fu = TemplateInfo(None, None)
    fu.max_occurs_under_parent = max_occurs_under_parent
    fu.resolve_hierarchy = resolve_hierarchy
    fu.METADATA_OBJECT_IGNORE = config.METADATA_OBJECT_IGNORE
    fu.METADATA_COUNT_IGNORE = config.METADATA_COUNT_IGNORE
    fu.BACK_REFERENCE_CLASS_NAME_OVERRIDE = config.BACK_REFERENCE_CLASS_NAME_OVERRIDE
    fu.ABSTRACT_PROPRIETARY_OVERRIDE = config.ABSTRACT_PROPRIETARY_OVERRIDE
    fu.ANNOTATION_OVERRIDE = config.ANNOTATION_OVERRIDE
    fu.DEFAULT_BASE_CLASS = model.opts.lang.getDefaultModelBaseClass()
    fu.OBJECT_HEADERS = sorted(object_headers);

    base, filename = os.path.split(template)
    name, extension = os.path.splitext(filename)
    used_files.add(os.path.realpath(template))
    template = NewTextTemplate(codecs.open(template, encoding='utf-8').read(), encoding='utf-8')
    from glob import glob
    customContent = dict()
    for customTemplate in glob(os.path.join(base, '%s_*' % (name))):
        filename = os.path.split(customTemplate)[1];
        filename = os.path.splitext(filename)[0]
        base, objectName, propertyName = filename.split("_")
        used_files.add(os.path.realpath(customTemplate))
        customTemplate = NewTextTemplate(codecs.open(customTemplate, encoding='utf-8').read(), encoding='utf-8')
        customTemplate = customTemplate.generate(
            fu=fu, model=model, parents=parents, is_multi_path=is_multi_path,
            indexes=indexes, opts=opts, lang=opts.lang, debug=opts.debug)
        if objectName not in customContent:
            customContent[objectName] = dict()
        customContent[objectName][propertyName] = customTemplate.render(encoding='utf-8')

    fullname = opts.lang.generatedFilename(outputFilename, opts.filetype)

    fu.GUARD = guard(fullname)
    fu.SOURCE_TYPE = opts.filetype;

    fullname = os.path.join(opts.outdir, fullname)

    if opts.print_generated:
        print(fullname)

    outputDirectory = os.path.dirname(fullname)
    if not opts.dryrun and not os.path.exists(outputDirectory):
        os.makedirs(outputDirectory)

    content = template.generate(fu=fu, model=model, parents=parents,
                                is_multi_path=is_multi_path, indexes=indexes,
                                customContent=customContent,
                                opts=opts, lang=opts.lang, debug=opts.debug).render(encoding='utf-8')
    if not opts.dryrun:
        f = codecs.open(fullname, "w", encoding='utf-8')
        f.write(content.decode('utf-8'))
        f.close()

    if opts.print_depends:
        for file in sorted(used_files):
            print(file)

def omeXmlMetadataMain(model, opts):
    """
    OME-XML main() that parses command line options and generates a
    MetadataStore and MetadataRetrieve implementation.
    """

    relpath = os.path.join(*opts.lang.omexml_metadata_package.split(opts.lang.package_separator))

    processMetadataTemplate('OMEXML_METADATA',
                            os.path.join(relpath, os.path.basename(os.path.splitext(opts.lang.getTemplate('OMEXML_METADATA'))[0])),
                            model, opts)

def omeroMetadataMain(model, opts):
    """
    Default main() that parses command line options and generates a
    MetadataRetrieve implementation.
    """

    processMetadataTemplate('OMERO_METADATA', "OmeroMetadata",
                            model, opts)

def omeroModelMain(model, opts):
    """
    Default main() that parses command line options and generates a
    MetadataRetrieve implementation.
    """

    processMetadataTemplate('OMERO_MODEL', "OmeroModel.xml",
                            model, opts)

def metadataMain(model, opts):
    """
    Default main() that parses command line options and generates a
    MetadataStore implementation.
    """

    relpath = os.path.join(*opts.lang.metadata_package.split(opts.lang.package_separator))

    processMetadataTemplate('METADATA_STORE',
                os.path.join(relpath, "MetadataStore"),
                model, opts)

    processMetadataTemplate('METADATA_RETRIEVE',
                os.path.join(relpath, "MetadataRetrieve"),
                model, opts)

    processMetadataTemplate('METADATA_AGGREGATE',
                os.path.join(relpath, "AggregateMetadata"),
                model, opts)

    processMetadataTemplate('FILTER_METADATA',
                os.path.join(relpath, "FilterMetadata"),
                model, opts)

    processMetadataTemplate('DUMMY_METADATA',
                os.path.join(relpath, "DummyMetadata"),
                model, opts)

def docGenMain(model, opts):
    """
    Documentation main() that does not parse the command line and spits
    generated wiki formatted docs to STDOUT.
    Added by Andrew Patterson (July 2008) to generate content for
    the ArityOfSchema page on http://ome-xml.org
    """

    for theSchemas in [
    ["OME",           "../specification/InProgress/ome.xsd"],
    ["SPW",           "../specification/InProgress/SPW.xsd"],
    ["BinaryFile",    "../specification/InProgress/BinaryFile.xsd"],
    ["SA",            "../specification/InProgress/SA.xsd"],
    ["ROI",           "../specification/InProgress/ROI.xsd"]
    ]:
        prefix = theSchemas[0] + ":"
        opts.args = [theSchemas[1]]
        fu = TemplateInfo(opts.outdir, opts.lang.omexml_model_package)
        template = NewTextTemplate(codecs.open(opts.lang.templatepath('CLASS'), encoding='utf-8').read(), encoding='utf-8')
        print(" == `%s` ==" % (prefix))
        for obj in model.objects.values():
            print(" === %s%s ===" % (prefix, obj.name))
            print(" * %s%s(base = `%s`, type = `%s`)" % (prefix, obj.name, obj.base, obj.type))
            for prop in obj.properties.values():
                print("   * %s%s:%s( `%s` ) ![%d:%d]" % \
                    (prefix, obj.name, prop.name, prop.type, prop.minOccurs, prop.maxOccurs))
            print("\n")

def tabGenMain(model, opts):
    """
    Documentation main() that does not parse the command line and puts
    tab sperated format to STDOUT.
    Added by Andrew Patterson (October 2008) to generate content for arity spreadsheet
    """

    for theSchemas in [
            ["OME",       "../specification/InProgress/ome.xsd",
                [["OME", "OME:"]]],
            ["ROI",       "../specification/InProgress/ROI.xsd",
                [["ROI", "OME:ROI:"]]],
            ["SPW",       "../specification/InProgress/SPW.xsd",
                [["Plate", "OME:Plate:"],
                ["Screen", "OME:Screen:"]]],
            ["SA",        "../specification/InProgress/SA.xsd",
                [["StructuredAnnotations", "OME:StructuredAnnotations:"]]],
            ["BinaryFile","../specification/InProgress/BinaryFile.xsd",
                [["BinaryFile", "OME:Instrument:OTF:BinaryFile:"]]]
        ]:
        prefix = theSchemas[0] + ":"
        opts.args = [theSchemas[1]]
        fu = TemplateInfo(opts.outdir, opts.lang.omexml_model_package)
        template = NewTextTemplate(codecs.open(opts.lang.templatepath('CLASS'), encoding='utf-8').read(), encoding='utf-8')
        for theToplevelNode in theSchemas[2]:
            parentlist(model, theToplevelNode[0], theToplevelNode[1])

def parentlist(model, name, pre):
    for obj in model.objects.values():
        if obj.name in [name]:
            #print pre + name;
            for prop in obj.properties.values():
                if prop.name[-14:] == "_BackReference":
                    print("-\t-\t" + prop.name + "\t" + prop.type + "\tback\t" + pre + prop.name)
                    pass
                else:
                    if prop.name == name:
                        print("%s\t%s" % (prop.minOccurs, prop.maxOccurs ) + "\t" + prop.name + "\t" + prop.type + "\trecursion\t" + pre + prop.name + " - recursion")
                    else:
                        print("%s\t%s" % (prop.minOccurs, prop.maxOccurs ) + "\t" + prop.name + "\t" + prop.type + "\t\t" + pre + prop.name)
                        parentlist(model, prop.type, pre + prop.name + ":")


class options:
    def __init__(self, args):
        try:
            self.options, self.args = gnu_getopt(args[1:], "df:l:o:p:n:t:qv",
                                                 ['dry-run',
                                                  'debug',
                                                  'file-type=',
                                                  'language=',
                                                  'output-directory=',
                                                  'ome-xml-model-package=',
                                                  'ome-xml-model-enums-package=',
                                                  'ome-xml-model-enum-handlers-package=',
                                                  'metadata-package=',
                                                  'ome-xml-metadata-package=',
                                                  'print-depends',
                                                  'print-generated',
                                                  'xsd-namespace=',
                                                  'template-path=',
                                                  'quiet',
                                                  'verbose'])
        except GetoptError as err:
            usage(err)

        omexml_model_package = None
        omexml_model_enums_package = None
        omexml_model_omexml_model_enum_handlers_package = None
        metadata_package = None
        omexml_metadata_package = None

        self.outdir = None
        self.namespace = config.DEFAULT_NAMESPACE
        self.lang = "Java"
        self.filetype = language.TYPE_SOURCE
        self.templatepath = os.path.dirname(os.path.realpath(__file__))
        self.verbose = True
        self.dryrun = False
        self.debug = False
        self.print_generated = False
        self.print_depends = False
        self.print_generated = False
        for option, argument in self.options:
            if option in ('-f', '--file-type'):
                if argument == language.TYPE_SOURCE:
                    self.filetype = language.TYPE_SOURCE
                elif argument == language.TYPE_HEADER:
                    self.filetype = language.TYPE_HEADER
                else:
                    usage("Invalid filetype; source and header are the only valid options")
                    sys.exit(1)
            elif option in ('-l', '--language'):
                self.lang = argument
            elif option in ('-o', '--output-directory'):
                self.outdir = argument
            elif option in ('--ome-xml-model-package'):
                omexml_model_package = argument
            elif option in ('--ome-xml-model-enums-package'):
                omexml_model_enums_package = argument
            elif option in ('--ome-xml-model-enum-handlers-package'):
                omexml_model_omexml_model_enum_handlers_package = argument
            elif option in ('--metadata-package'):
                metadata_package = argument
            elif option in ('--ome-xml-metadata-package'):
                omexml_metadata_package = argument
            elif option in ('-n', '--xsd-namespace'):
                self.namespace = argument
            elif option in ('-t', '--template-path'):
                self.templatepath = argument
            elif option in ('-q', '--quiet'):
                self.verbose = False
                self.print_depends = False
                self.print_generated = False
            elif option in ('-v', '--verbose'):
                self.verbose = True
                self.print_depends = False
                self.print_generated = True
            elif option in ('-d', '--dry-run'):
                self.dryrun = True
            elif option in ('--debug'):
                # Note due to gnu_getopt bug, this must be placed
                # after -d or else -d is treated as being equivalent
                # to --debug.
                self.debug = True
            elif option in ('--print-depends'):
                self.print_depends = True
            elif option in ('--print-generated'):
                self.print_generated = True
        # Create language object
        self.lang = language.create(self.lang, self.namespace, self.templatepath)
        if omexml_model_package is not None:
            self.lang.omexml_model_package = omexml_model_package
        if omexml_model_enums_package is not None:
            self.lang.omexml_model_enums_package = omexml_model_enums_package
        if omexml_model_omexml_model_enum_handlers_package is not None:
            self.lang.omexml_model_omexml_model_enum_handlers_package = omexml_model_omexml_model_enum_handlers_package
        if metadata_package is not None:
            self.lang.metadata_package = metadata_package
        if omexml_metadata_package is not None:
            self.lang.omexml_metadata_package = omexml_metadata_package

        if len(self.args) < 1:
            usage('Missing subcommand')

        if self.outdir is None:
            usage("Output directory must be specified")
            sys.exit(1)
        if not self.dryrun and not os.path.exists(self.outdir):
            os.makedirs(self.outdir)

        self.command = self.args[0]
        self.args = self.args[1:]

        if (self.command != "doc_gen" and self.command != "tab_gen") and len(self.args) < 1:
            usage("No schema files specified")

if __name__ == '__main__':
    opts = options(sys.argv)
    model = generateds.parse(opts)

    if opts.command == "omexml_model_all":
        main(model, opts)
        enumTypesMain(model, opts)
        if isinstance(opts.lang, language.CXX) and opts.filetype == language.TYPE_HEADER:
            enumTypesIncludeAll(model, opts)
        if isinstance(opts.lang, language.Java):
            enumHandlersMain(model, opts)
    elif opts.command == "omexml_metadata_all":
        metadataMain(model, opts)
        omeXmlMetadataMain(model, opts)
    elif opts.command == "omexml_model":
        main(model, opts)
    elif opts.command == "omexml_metadata":
        omeXmlMetadataMain(model, opts)
    elif opts.command == "omero_metadata":
        omeroMetadataMain(model, opts)
    elif opts.command == "omero_model":
        omeroModelMain(model, opts)
    elif opts.command == "metadata":
        metadataMain(model, opts)
    elif opts.command == "omexml_model_enums":
        enumTypesMain(model, opts)
    elif opts.command == "omexml_model_enum_includeall":
        enumTypesIncludeAll(model, opts)
    elif opts.command == "omexml_model_enum_handlers":
        enumHandlersMain(model, opts)
    elif opts.command == "doc_gen":
        docGenMain(model, opts)
    elif opts.command == "tab_gen":
        tabGenMain(model, opts)
    else:
        usage('Unrecognized subcommand: %s' % opts.command)
